/**
 * https://www.cnblogs.com/Seiyagoo/p/3496945.html
 * https://blog.csdn.net/ice__snow/article/details/79979298
 * https://blog.csdn.net/lvbian/article/details/23031635
 * 
 * This code is collected and generalized by hytzongxuan (hytzongxuan@gmail.com)
 */

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <cstdint>
#include <sys/types.h>
#include <dirent.h>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <list>
#include <map>

#define BUF_SIZE 1024
using namespace std;

class WindowsMatchingPid
{
  public:
    WindowsMatchingPid(Display *display, Window wRoot, unsigned long pid)
        : _display(display), _pid(pid)
    {
        _atomPID = XInternAtom(display, "_NET_WM_PID", True);
        if (_atomPID == None)
        {
            cout << "No such atom" << endl;
            return;
        }

        search(wRoot);
    }

    const list<Window> &result() const { return _result; }

  private:
    unsigned long _pid;
    Atom _atomPID;
    Display *_display;
    list<Window> _result;

    void search(Window w)
    {
        Atom type;
        int format;
        unsigned long nItems;
        unsigned long bytesAfter;
        unsigned char *propPID = 0;
        if (Success == XGetWindowProperty(_display, w, _atomPID, 0, 1, False, XA_CARDINAL,
                                          &type, &format, &nItems, &bytesAfter, &propPID))
        {
            if (propPID != 0)
            {
                if (_pid == *((unsigned long *)propPID))
                    _result.push_back(w);

                XFree(propPID);
            }
        }

        Window wRoot;
        Window wParent;
        Window *wChild;
        unsigned nChildren;
        if (0 != XQueryTree(_display, w, &wRoot, &wParent, &wChild, &nChildren))
        {
            for (unsigned i = 0; i < nChildren; i++)
                search(wChild[i]);
        }
    }
};

std::list<std::string> getPidByName(const char *task_name)
{
    std::list<std::string> pids;

    DIR *dir;
    struct dirent *ptr;
    FILE *fp;
    char buf[BUF_SIZE];

    dir = opendir("/proc");

    if (NULL != dir)
    {
        while ((ptr = readdir(dir)) != NULL)
        {

            if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0))
                continue;
            if (DT_DIR != ptr->d_type)
                continue;

            sprintf(buf, "/proc/%s/cmdline", ptr->d_name);
            fp = fopen(buf, "r");

            if (NULL != fp)
            {
                if (fgets(buf, BUF_SIZE - 1, fp) == NULL)
                {
                    fclose(fp);
                    continue;
                }
//                sscanf(buf, "%s", cur_task_name);

//                std::cout << buf << std::endl;
                if (nullptr != ::strstr(buf, task_name))
                {
                    pids.emplace_back(ptr->d_name);
                }

                fclose(fp);
            }
        }
        closedir(dir);
    }
    return pids;
}

struct SeperatedShadowWindow
{
    Window window;
    int height, width;

    SeperatedShadowWindow() {}
    SeperatedShadowWindow(Window _win, int _h, int _w)
    {
        window = _win;
        height = _h;
        width = _w;
    }
};

void findSeperatedShadowWindow(std::list<Window>& shadowWindows, const string& x, Display* display)
{
    std::list<SeperatedShadowWindow> windows;
    map<pair<int, int>, int> sizeMap;

    int pid = atoi(x.c_str());

    WindowsMatchingPid match(display, XDefaultRootWindow(display), pid);
    const list<Window> &result = match.result();
    for (auto it : result) {
        XWindowAttributes attrs;
        XGetWindowAttributes(display, it, &attrs);

        sizeMap[make_pair(attrs.height, attrs.width)]++;
        windows.emplace_back(it, attrs.height, attrs.width);

//        std::cout << "WinID: 0x" << std::hex << it << ", " << std::dec << attrs.width << "x" << attrs.height << std::endl;
    }

    for (const auto& wnd : windows) {
        if (2 == sizeMap[make_pair(wnd.height, wnd.width)]) {
            shadowWindows.emplace_back(wnd.window);
        }
    }
}

void findSingleShadowWindow(std::list<Window>& shadowWindows, const string& x, Display* display) {
    int pid = atoi(x.c_str());

    WindowsMatchingPid match(display, XDefaultRootWindow(display), pid);
    const list<Window> &result = match.result();
    if (1 >= result.size()) {
        return;
    }

    auto it = result.begin();

    XWindowAttributes attrsMain;
    XGetWindowAttributes(display, *it, &attrsMain);

//    std::cout << "WinID: 0x" << std::hex << *it << ", " << std::dec
//            << attrsMain.x << ", " << attrsMain.y << ", " << attrsMain.width << "x" << attrsMain.height << std::endl;

    for (++it; it != result.end(); ++it) {
        XWindowAttributes attrs;
        XGetWindowAttributes(display, *it, &attrs);

        if (attrs.width > attrsMain.width && attrs.height > attrsMain.height) {
            shadowWindows.emplace_back(*it);
        }

//        std::cout << "WinID: 0x" << std::hex << *it << ", " << std::dec
//            << attrs.x << ", " << attrs.y << ", " << attrs.width << "x" << attrs.height << std::endl;
    }
}

int main(int argc, char **argv)
{
    if (2 < argc) {
        std::cout << "Usage: " << argv[0] << " <program.exe>" << std::endl;
        return -1;
    }
    auto* display = XOpenDisplay(NULL);
    if (display == NULL)
    {
        std::cout << "Cannot open display" << std::endl;
        return -1;
    }

    auto pids = getPidByName(argv[1]);
    if (pids.empty()) {
        std::cout << "Program not found" << std::endl;
        return -1;
    }

    std::list<Window> shadowWindows;
    for (const auto& pid : pids) {
        if (0 == ::strcmp(argv[1], "cloudmusic.exe")) {
            findSeperatedShadowWindow(shadowWindows, pid, display);
        } else {
            findSingleShadowWindow(shadowWindows, pid, display);
        }
    }

    if (shadowWindows.empty()) {
        std::cout << "Shadow window not found" << std::endl;
        return -1;
    }

    auto s = DefaultScreen(display);

    auto window = XCreateSimpleWindow(display, RootWindow(display, s), 10, 10, 1, 1, 1,
                                 BlackPixel(display, s), WhitePixel(display, s));

    XSelectInput(display, window, ExposureMask | KeyPressMask);

    XMapWindow(display, window);

    for (const auto& wnd : shadowWindows) {
        XReparentWindow(display, wnd, window, 1, 1);
    }

    XCloseDisplay(display);
    return 0;
}